use std::collections::HashMap;
use std::rc::Rc;

pub mod db;
use db::*;

use tumo::{core::layout::*, geom::*, main::*, mods::anim::*, mods::*, offer::*, prelude::*, widget::*};

use inner::*;

static RETURN_SVG: &str = include_str!("../assets/return.svg");
static MORE_SVG: &str = include_str!("../assets/more.svg");

#[derive(Default)]
pub struct Main {
	db: Observer<FontDB>,
	font: Observer<Option<inner::Font>>,
	tessors: HashMap<u32, (usize, f32, tumo::core::show::Path)>,
}
impl TumoMain<tumo_xloop::prelude::WinRaw> for Main {
	fn main(cx: &mut Cx<'_, Env>) {
		ColorTheme::material3_system(cx).offer(cx);
		Elevation::absolute(0).offer(cx);
		ColorBack::surface(cx).offer(cx);
		ColorFront::surface(cx).offer(cx);
		let db = cx.db.clone();
		cx.spawn(0, async move {
			*db.borrow_mut() = blocking::unblock(|| {
				let mut db = FontDB::default();
				db.load_system(false);
				db
			})
			.await;
			db.notify();
		});
		Tn::Rout(cx, |cx| {
			_ = Router::Page(cx, "home", home);
			_ = Router::Page(cx, "font/{id}", font);
			_ = Router::Page(cx, "info", info);
			_ = Router::Page(cx, "letter/{code}", letter);
			Router::Home(cx, "home");
		});
	}
	type Gpu = tumo_glow::TumoGlow;
	type Env = Env;
	fn create_env(w: core::task::Waker) -> Self::Env {
		let mut core = TumoCore::new(w);
		let base = TumoBase::new(&mut core);
		Env::new(core, Main::default(), base)
	}
	fn create_gpu() -> Self::Gpu {
		tumo_glow::TumoGlow::new()
	}
}
impl tumo_xloop::HasWinAttr for Main {
	fn create_win_attr() -> <tumo_xloop::prelude::Window as tumo_xloop::prelude::xloop_types::Win>::Attr {
		tumo_xloop::xloop::xloop_types::XwinAttr {
			title: "tumofont".into(),
			..Default::default()
		}
	}
}

#[allow(dead_code)]
fn main() {
	puffin::set_scopes_on(true);
	let _p = puffin_http::Server::new("127.0.0.1:8585");
	use tumo_xloop::prelude::*;
	Application::log_info();
	Application::run(AppDelegate::<Main>::default()).ok();
}
DefineEnv!(
	pub struct Env {
		main: Main,
		base: TumoBase,
	}
);
impl core::ops::Deref for Env {
	type Target = Main;
	fn deref(&self) -> &Self::Target {
		&self.main
	}
}
impl core::ops::DerefMut for Env {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.main
	}
}

fn home(cx: &mut Cx<'_, Env>, router: Rc<Router<Env>>, _: router::Status, _: &HashMap<String, String>) {
	router::RouterAnimShow(AnimShift::left_in()).m(cx);
	Tn::ScrollVertical(cx, Axis::column().gap((10., 10.)), |cx, scroller| {
		let db = cx.db.clone().bind(cx);
		for (id, info) in db.borrow().faces().enumerate() {
			let router = router.clone();
			Tc::ContainK(cx, scroller.a(info.unique_id.clone()), |cx| {
				scroller.bind(cx);
				let font = cx.font.clone();
				Elevation::LEVEL_1.offer(cx);
				cx.m(Size(1., 120.dp())).m(PadBoth(0.dp(), 5.dp())).on(TapPrimary(move |_| {
					*font.borrow_mut() = None;
					_ = router.push(format!("font/{}", id));
				}));
				Tc::ContainM(cx, (FULL, PadAll(10.dp())), |cx| {
					cx.m(EBG);
					Tc::ContainM(cx, Column, |cx| {
						Tc::ContainM(cx, Size(1., [0.dp()]), |cx| {
							FamilyTextFixed::new(&info.families.first().map(|v| v.0.clone()).unwrap_or_default())
								.brush(ColorFront::current(cx).value())
								.m(cx);
						});
						Tc::ContainM(cx, Size(1., [0.dp()]), |cx| {
							FamilyTextFixed::new(&match &info.source {
								Source::File(path) => format!("{} [ {} ]", path.display(), info.index),
								_ => "".to_string(),
							})
							.brush(ColorFront::current(cx).value())
							.size(16.dp())
							.m(cx);
						});
					});
					Tc::ContainM(cx, POS_MR_MR, |cx| {
						cx.m(Column);
						Tc::Contain(cx, |cx| {
							FamilyTextFixed::new(&format!("{}", info.weight))
								.brush(ColorFront::current(cx).value())
								.size(16.dp())
								.m(cx);
						});
						Tc::Contain(cx, |cx| {
							FamilyTextFixed::new(&format!("{}", info.stretch))
								.brush(ColorFront::current(cx).value())
								.size(16.dp())
								.m(cx);
						});

						Tc::Contain(cx, |cx| {
							FamilyTextFixed::new(&format!("{}", info.style))
								.brush(ColorFront::current(cx).value())
								.size(16.dp())
								.m(cx);
						});
					});
				});
			});
		}
	});
}
fn font(cx: &mut Cx<'_, Env>, router: Rc<Router<Env>>, status: router::Status, params: &HashMap<String, String>) {
	router::RouterAnim(status, AnimShift::right_in().anim(Some([400].cubic_out())).clone()).m(cx);
	let left_width: Unit = 80.dp();
	let code_base = 0u32.state(cx);
	let Some(fid) = params.get("id").map(|id| id.parse().ok()).flatten() else {
		return;
	};
	let font = cx.font.clone().bind(cx);
	cx.spawn(0, {
		let font = font.clone();
		let info = cx.db.borrow().face(fid).cloned();
		async move {
			if let Some(info) = info {
				*font.borrow_mut() = blocking::unblock(move || Font::load(info)).await;
				font.notify();
			}
		}
	});
	Tu::Scaffold(cx, |cx| {
		Scaffold::ActionBar(cx, |cx| {
			cx.m(FULL).m(EBG).m(FlexRow);
			let color_front = ColorFront::current(cx).value();
			Tu::Spacer(cx, Size(10.dp(), 10.dp()));
			Tc::ContainM(cx, (Size(48.dp(), 48.dp()), POS_ML_ML), |cx| {
				cx.on(TapPrimary({
					let router = router.clone();
					move |_| {
						_ = router.back();
					}
				}));
				RasterIcon::new(IconStr("return.svg", RETURN_SVG).mono(cx), color_front).apply(cx);
			});
			Tc::ContainM(cx, FULL, |cx| {
				Tc::ContainM(cx, POS_MC_MC, |cx| {
					let name = font.borrow().as_ref().map(|font| font.info.full_name.clone()).unwrap_or_default();
					FamilyTextFixed::new(&format!("{}", name)).brush(color_front).m(cx);
				});
			});
			Tc::ContainM(cx, (Size(48.dp(), 48.dp()), POS_ML_ML), |cx| {
				cx.on(TapPrimary({
					let router = router.clone();
					move |_| {
						_ = router.push("info");
					}
				}));
				RasterIcon::new(IconStr("more.svg", MORE_SVG).mono(cx), color_front).apply(cx);
			});
			Tu::Spacer(cx, Size(10.dp(), 10.dp()));
		});
		Scaffold::Content(cx, |cx| {
			cx.m(FULL).m(EBG).m(FlexColumn);
			Tc::ContainM(cx, Size(1., 50.dp()), |cx| {
				let theme = ColorTheme::current(cx);
				cx.m(FlexRow);
				cx.m(Prim([0], theme.secondary));
				Tc::ContainNode(cx, Size(left_width, 1.));
				for column in 0..16u8 {
					Tc::ContainM(cx, FULL, |cx| {
						Tc::ContainM(cx, POS_MC_MC, |cx| {
							FamilyTextFixed::new(&format!("{:02X}", column))
								.brush(theme.on_secondary)
								.size(20.dp())
								.m(cx);
						});
					});
				}
			});
			Tc::Silver(cx, FULL, move |cx, _w, h| {
				cx.m(LDirty);
				let row_num = 16u32;
				let height_px = (h * 0.125).round();
				let height_stay = height_px.stay_cell(cx);
				height_stay.set(height_px);
				let code_base_v = code_base.get(cx);
				let base_px = height_px * (code_base_v / row_num) as f32;
				let init = |scroller: &mut Scroller<u32>| {
					scroller.vertical();
					let code = code_base.clone();
					scroller.on_scroll(move |_, new, _strict| {
						let old_code = code.peek();
						let height_px = height_stay.get();
						let base_px = (old_code / row_num) as f32 * height_px;
						let end_px = base_px + 8. * height_px;
						if new.y > end_px - height_px {
							let mut new_code = old_code.saturating_add(4 * row_num);
							loop {
								let base_px = (new_code / row_num) as f32 * height_px;
								let end_px = base_px + 8. * height_px;
								if new.y > end_px - height_px {
									new_code = new_code.saturating_add(4 * row_num);
									continue;
								}
								break;
							}
							if new_code != old_code {
								code.set(new_code);
							}
						}
						if new.y < base_px + height_px {
							let mut new_code = old_code.saturating_sub(4 * row_num);
							loop {
								let base_px = (new_code / row_num) as f32 * height_px;
								if new.y < base_px + height_px && base_px > 0. {
									new_code = new_code.saturating_sub(4 * row_num);
									continue;
								}
								break;
							}
							if new_code != old_code {
								code.set(new_code);
							}
						}
						scroll::Return::None
					});
				};
				Tn::ScrollInitK(cx, cx.a(), Axis::column().base((0., base_px)), init, |cx, scroller| {
					cx.m(FULL);
					for row in 0..16u32 {
						Tc::ContainKM(cx, scroller.a(code_base_v), Size(1., height_px.px()), |cx| {
							scroller.bind(cx);
							let theme = ColorTheme::current(cx);
							cx.layout(Flex::row());
							cx.m(Prim([0], theme.secondary));
							Tc::ContainM(cx, Size(left_width, 1.), |cx| {
								Tc::ContainM(cx, POS_MC_MC, |cx| {
									FamilyTextFixed::new(&format!("{:>6X}", code_base_v + row * 16))
										.brush(theme.on_secondary)
										.size(20.dp())
										.m(cx);
								});
							});
							for column in 0..row_num {
								Tc::ContainM(cx, FULL, |cx| {
									let color = ColorFront::current(cx).value();
									let code = code_base_v + row * 16 + column;
									let size = 32.dp();
									let elevation = if row % 2 == column % 2 {
										Elevation::absolute(1)
									} else {
										Elevation::absolute(2)
									};
									elevation.offer(cx);
									cx.m(EBG);
									let px = size.px(cx);
									let rekey = H!((code, font.borrow().is_some()));
									let path = cx.effect_return(rekey, |env| {
										let code = if code <= 0x1F { 0x2400 + code } else { code };
										if let Some((ofid, size, path)) = env.tessors.get(&code) {
											if fid == *ofid && size.approx_eq(&px) {
												return Some(path.clone());
											}
										}
										if let Some(path) = font.borrow_mut().as_mut().map(|font| font.tessor(code, px)).flatten() {
											env.tessors.insert(code, (fid, px, path.clone()));
											return Some(path);
										}
										None
									});
									Tc::ContainOption(cx, path, |cx, path| {
										cx.m((Size(size, size), POS_MC_MC));
										Elevation::absolute(3).offer(cx);
										Prim([0], Elevation::current(cx).background(cx)).m(cx);
										Prim(path, color).m(cx);
										cx.listen(
											rekey,
											TapPrimary({
												let router = router.clone();
												move |_| {
													_ = router.push(format!("letter/{}", code));
												}
											}),
										);
									});
									Tc::ContainM(cx, POS_BR_BR, |cx| {
										FamilyTextFixed::new(&format!("U+{:04X}", code))
											.brush(color.with_alpha(0.2))
											.size(12.dp())
											.m(cx);
									});
								});
							}
						});
					}
				});
			});
		});
	});
}
fn info(cx: &mut Cx<'_, Env>, router: Rc<Router<Env>>, status: router::Status, _params: &HashMap<String, String>) {
	router::RouterAnim(status, AnimShift::right_in().anim(Some([400].cubic_out())).clone()).m(cx);
	let font = cx.font.clone().bind(cx);
	Tu::Scaffold(cx, |cx| {
		Scaffold::ActionBar(cx, |cx| {
			cx.m(FULL).m(EBG).m(Row);
			Tu::Spacer(cx, Size(10.dp(), 10.dp()));
			Tc::ContainM(cx, (Size(48.dp(), 48.dp()), POS_ML_ML), |cx| {
				cx.on(TapPrimary({
					let router = router.clone();
					move |_| {
						_ = router.back();
					}
				}));
				let icon_uuid = IconStr("return.svg", RETURN_SVG).mono(cx);
				RasterIcon::new(icon_uuid, ColorFront::current(cx).value()).apply(cx);
			});
		});
		Scaffold::Content(cx, |cx| {
			let color = ColorFront::current(cx).value();
			cx.m(FULL).m(EBG).m(Column);
			if let Some(font) = &*font.borrow() {
				Tc::ContainNode(cx, FamilyTextFixed::new(&format!("{:?}", font.info.families)).brush(color));
				Tc::ContainNode(cx, FamilyTextFixed::new(&font.info.full_name).brush(color));
				Tc::ContainNode(cx, FamilyTextFixed::new(&font.info.post_script_name).brush(color));
				Tc::ContainNode(cx, FamilyTextFixed::new(&font.info.unique_id).brush(color));
				Tc::ContainNode(cx, FamilyTextFixed::new(&font.info.dlng).brush(color));
				Tc::ContainNode(cx, FamilyTextFixed::new(&font.info.slng).brush(color));
			}
		});
	});
}
fn letter(cx: &mut Cx<'_, Env>, router: Rc<Router<Env>>, status: router::Status, params: &HashMap<String, String>) {
	router::RouterAnim(status, AnimShift::right_in().anim(Some([400].cubic_out())).clone()).m(cx);
	let font = cx.font.clone().bind(cx);
	let Some(font) = &mut *font.borrow_mut() else {
		return;
	};
	let color = ColorFront::current(cx).value();
	let em_px = 300.dp().px(cx);
	let units_per_em = font.face.units_per_em() as f32;
	let ascender = font.face.ascender() as f32;
	let descender = font.face.descender() as f32;
	let ascender_px = ascender / units_per_em * em_px;
	let descender_px = descender / units_per_em * em_px;
	let height_px = em_px * 3.0;
	let width_px = em_px * 3.0;
	let baseline = em_px * 2.0;
	let code = params.get("code").map(|v| v.parse().ok()).flatten().unwrap_or(0u32);
	cx.m(FULL).on(router::RouterTapPrimaryBack(router.clone()));
	Tc::ContainM(cx, POS_MC_MC, |cx| {
		Elevation::LEVEL_1.offer(cx);
		cx.m(Size(width_px.px(), height_px.px())).m(EBG);
		Tc::Contain(cx, |cx| {
			Size(1., 2.px()).m(cx);
			Pos(([0], [baseline - ascender_px]), Anchor2D::BOTTOM_LEFT).m(cx);
			PrimRect(color).m(cx);
		});
		Tc::Contain(cx, |cx| {
			Pos(([0], [baseline - ascender_px]), Anchor2D::BOTTOM_LEFT).m(cx);
			Tc::ContainNode(cx, FamilyTextFixed::new("ascender").brush(color).size(16.dp()));
		});
		Tc::Contain(cx, |cx| {
			Size(1., 2.px()).m(cx);
			Pos(([0], [baseline - descender_px]), Anchor2D::BOTTOM_LEFT).m(cx);
			PrimRect(color).m(cx);
		});
		Tc::Contain(cx, |cx| {
			Pos(([0], [baseline - descender_px]), Anchor2D::BOTTOM_LEFT).m(cx);
			Tc::ContainNode(cx, FamilyTextFixed::new("descender").brush(color).size(16.dp()));
		});
		Tc::Contain(cx, |cx| {
			Elevation::LEVEL_2.offer(cx);
			cx.m(Size(300.dp(), 300.dp()));
			cx.m(Pos(([em_px], [baseline]), Anchor2D::BOTTOM_LEFT));
			let code = if code <= 0x1F { 0x2400 + code } else { code };
			let path = cx.effect_return(H!((code)), |_| font.tessor(code, em_px));
			if let Some(path) = path {
				Prim([0], Elevation::current(cx).background(cx).with_alpha(0.5)).m(cx);
				Prim(path, color).m(cx);
			}
		});
	});
}

pub mod inner {
	use std::collections::HashMap;
	use std::sync::Arc;

	use super::db::*;
	use tumo::base::memmap2;
	use tumo::base::path::Path;
	use tumo::base::rustybuzz::Face;
	use tumo::base::tessor;

	#[derive(Clone)]
	pub struct Char {
		pub code_point: u32,
		pub table_id: usize,
		pub glyph_id: u16,
		pub path: Arc<Path>,
	}

	pub struct Font {
		_trapezoidator: tessor::Trapezoidator,
		_mmap: memmap2::Mmap,
		pub info: FaceInfo,
		pub face: Face<'static>,
		pub chars: HashMap<u32, Char>,
	}
	impl Font {
		pub fn tessor(&mut self, code: u32, size: f32) -> Option<tumo::core::show::Path> {
			let path = &self.chars.get(&code)?.path;
			let scale = size / self.face.units_per_em() as f32;
			let out = tumo::core::show::Path::default();
			{
				use tessor::PathIterator as _;
				struct PathWriter<'a>(&'a mut Vec<(f32, f32)>, &'a mut Vec<u32>, f32);
				impl<'a> tessor::Callbacks for PathWriter<'a> {
					fn vertex(&mut self, x: f32, y: f32) -> u16 {
						let ret = self.0.len();
						self.0.push((x * self.2, y * self.2));
						ret as _
					}
					fn triangle(&mut self, index_0: u16, index_1: u16, index_2: u16) {
						self.1.extend_from_slice(&[index_0 as u32, index_1 as u32, index_2 as u32]);
					}
				}
				let out_bind = &mut *out.0.borrow_mut();
				path.iter_tessor(None).trapezoidate(
					1.,
					&mut self._trapezoidator,
					&mut PathWriter(&mut out_bind.0, &mut out_bind.1, scale),
				);
			}
			Some(out)
		}
		pub fn char(&self, code: u32) -> Option<Char> {
			self.chars.get(&code).cloned()
		}
		pub fn load(info: FaceInfo) -> Option<Self> {
			use rayon::prelude::*;
			let path = match &info.source {
				Source::File(path) => path,
				_ => return None,
			};
			let index = info.index;
			let _mmap = unsafe { memmap2::MmapOptions::new().map(&std::fs::File::open(path).ok()?).ok() }?;
			let data = _mmap.as_ref() as *const [u8];
			let data: &'static [u8] = unsafe { &*data };
			let face = Face::from_slice(data, index)?;
			let mut chars = HashMap::new();
			for (table_id, subtable) in face.tables().cmap?.subtables.into_iter().enumerate() {
				if !subtable.is_unicode() {
					continue;
				}
				subtable.codepoints(|code_point| {
					if let Some(glyph_id) = subtable.glyph_index(code_point) {
						chars.insert(code_point, (table_id, glyph_id));
					}
				});
			}
			let chars = chars
				.par_iter()
				.filter_map(|(code_point, (table_id, glyph_id))| {
					let mut path = Path::default();
					if let Some(_) = face.outline_glyph(*glyph_id, &mut path.ttf_builder(face.units_per_em() as f32)) {
						Some((
							*code_point,
							Char {
								code_point: *code_point,
								table_id: *table_id,
								glyph_id: glyph_id.0,
								path: Arc::new(path),
							},
						))
					} else {
						None
					}
				})
				.collect();
			Some(Font {
				info,
				_mmap,
				_trapezoidator: tessor::Trapezoidator::new(),
				face,
				chars,
			})
		}
	}
}
